using System;
using System.Collections.Generic;
using Xunit.Sdk;
using System.Linq;

namespace SubSpec
{
    internal class ObservationExecutor
    {
        private readonly Func<IEnumerable<SpecificationPrimitive<Action>>> _observations;
        private readonly SpecificationPrimitive<ContextDelegate> _context;
        private readonly SpecificationPrimitive<Action> _do;

        private IDisposable _systemUnderTest;


        public ObservationExecutor(SpecificationPrimitive<ContextDelegate> context, SpecificationPrimitive<Action> @do, Func<IEnumerable<SpecificationPrimitive<Action>>> observations)
        {
            _observations = observations;
            _context = context;
            _do = @do;

            _systemUnderTest = null;
        }

        private static MethodResult ExecuteActionCommand(IMethodInfo method, string name, Action setupAction)
        {
            ITestCommand wrappedCommand = new ActionTestCommand(method, name, MethodUtility.GetTimeoutParameter(method), setupAction); ;

            wrappedCommand = new ExceptionAndOutputCaptureCommand(wrappedCommand, method);
            wrappedCommand = new TimedCommand(wrappedCommand);

            return wrappedCommand.Execute(null);
        }

        public IEnumerable<ITestCommand> ObservationCommands(string name, IMethodInfo method)
        {
            if (_observations().Count() == 0)
                yield break;

            bool setupExceptionOccurred = false;

            Action setupAction = () =>
            {
                try
                {
                    _systemUnderTest = _context.Execute();

                    if (_do != null)
                        _do.Execute();
                }
                catch (Exception)
                {
                    setupExceptionOccurred = true;
                    throw;
                }
            };

            yield return new ActionTestCommand(method, "{ " + name, 0, setupAction);


            foreach (var observation in _observations())
            {
                Action perform = () =>
                                 {
                                     if (setupExceptionOccurred)
                                         throw new ContextSetupFailedException( "Setting up Context failed" );

                                     observation.Execute();
                                 };

                yield return new ActionTestCommand(method, "\t- " + observation.Message, 0, perform);
            }

            Action tearDownAction = () =>
            {
                if (_systemUnderTest != null)
                    _systemUnderTest.Dispose();

                if (setupExceptionOccurred)
                    throw new ContextSetupFailedException( "Setting up Context failed, but Fixtures were disposed." );
            };

            yield return new ActionTestCommand(method, "} " + name, 0, tearDownAction);
        }
    }
}
